Open In Colab

In [0]:
################  FINAL PROJECT JAMIE DEANTONIS & RYAN MCNALLY #####################
###### DATASET HOSTED IN GOOGLE DRIVE @ THIS LINK:
###### https://drive.google.com/open?id=1iDNHD1Q0QrQ8EV0MnISyNHvx3bVOeBzs

###### THIS NOTEBOOK WILL RUN END TO END
###### SUMMARY: CLASSES AND METHODS FOR DATA PREPROCESSING AND MODEL BUILDING COME FIRST, SIMPLE EXECUTIONS AT THE BOTTOM
###### FINAL OUTPUT FILE WILL BE WRITTEN FOR YOU AND FILEPATH IS SPECIFIED IN THE FINAL CELL, FEEL FREE TO CHANGE THE FILENAME TO SAVE A NEW FILE TO TEST
In [0]:
# !git clone 
# https://gist.github.com/josephernest/3f22c5ed5dabf1815f16efa8fa53d476wav
# !pip install sox
# !sox old.wav -b 16 new.wav
# import josephernest's remake of scipy.io.wavfile with readable 24-bit
# https://gist.github.com/josephernest/3f22c5ed5dabf1815f16efa8fa53d476
# from wav import wavfile as wav_reader
!pip install pysoundfile
!pip install simpleaudio
!pip install playsound
Collecting pysoundfile
  Downloading https://files.pythonhosted.org/packages/2a/b3/0b871e5fd31b9a8e54b4ee359384e705a1ca1e2870706d2f081dc7cc1693/PySoundFile-0.9.0.post1-py2.py3-none-any.whl
Requirement already satisfied: cffi>=0.6 in /usr/local/lib/python3.6/dist-packages (from pysoundfile) (1.13.2)
Requirement already satisfied: pycparser in /usr/local/lib/python3.6/dist-packages (from cffi>=0.6->pysoundfile) (2.19)
Installing collected packages: pysoundfile
Successfully installed pysoundfile-0.9.0.post1
Collecting simpleaudio
  Downloading https://files.pythonhosted.org/packages/94/1b/4dc29653733202b68c09d9c6ca085cf67ac54859ee860647ef21ac1ff3dc/simpleaudio-1.0.4.tar.gz (2.0MB)
     |████████████████████████████████| 2.0MB 4.8MB/s 
Building wheels for collected packages: simpleaudio
  Building wheel for simpleaudio (setup.py) ... done
  Created wheel for simpleaudio: filename=simpleaudio-1.0.4-cp36-cp36m-linux_x86_64.whl size=2063918 sha256=a0a74c30e7f015a0b48e3ff4e68e4c4d456407c83e1bdb60bb3db264496d233f
  Stored in directory: /root/.cache/pip/wheels/64/16/f5/74445bada9e7defeb1d1198d00f5f17f3519f633216b5c9267
Successfully built simpleaudio
Installing collected packages: simpleaudio
Successfully installed simpleaudio-1.0.4
Collecting playsound
  Downloading https://files.pythonhosted.org/packages/f5/16/10d897b0a83fb4b05b03a63d7a2667ab75f857f67f7062fd447dd3f49bf7/playsound-1.2.2-py2.py3-none-any.whl
Installing collected packages: playsound
Successfully installed playsound-1.2.2
In [0]:
from pathlib import Path  # for file handling
from google.colab import drive  # for mounting the google drive
import os  # for file handling
import scipy.io.wavfile as wf  # for reading wav files NO WORK FOR 24BIT
import soundfile  # for converting 24 bit to 16 bit
from scipy import signal  # for the spectrogram
from scipy import misc
import matplotlib.pyplot as plt  # for plotting spectrograms
import numpy as np
import simpleaudio as sa  # good for playing the audio directly
from playsound import playsound  # also good for playing audio
import tensorflow as tf
from keras.layers import Dense, Dropout, Flatten, Conv2D
from keras.layers import MaxPooling2D, Concatenate
from keras.layers.advanced_activations import LeakyReLU
from keras import Input, Model
import scipy.misc  # for writing the fourier transform to file
import tensorflow
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D
from keras.layers import Concatenate
from keras.layers.advanced_activations import LeakyReLU
from keras import Input, Model, Sequential
import shutil  # for removing folders
print(tf.__version__)

The default version of TensorFlow in Colab will soon switch to TensorFlow 2.x.
We recommend you upgrade now or ensure your notebook will continue to use TensorFlow 1.x via the %tensorflow_version 1.x magic: more info.

1.15.0
Using TensorFlow backend.
In [0]:
drive.mount('/content/drive')  # mount the google drive
Go to this URL in a browser: https://accounts.google.com/o/oauth2/auth?client_id=947318989803-6bn6qk8qdgf4n4g3pfee6491hc0brc4i.apps.googleusercontent.com&redirect_uri=urn%3aietf%3awg%3aoauth%3a2.0%3aoob&response_type=code&scope=email%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdocs.test%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive.photos.readonly%20https%3a%2f%2fwww.googleapis.com%2fauth%2fpeopleapi.readonly

Enter your authorization code:
··········
Mounted at /content/drive
In [0]:
os.chdir('/content/drive/My Drive/Stems')  # directory of the stems
data_dir = Path('./Raw')  # formalize as a path-like object
print(data_dir)
Raw

song_obj

attributes:

stem_list: list of stem objects that fit into that song
audio: an audio object, the audio data for this song
fourier (optional): a fourier object, the fourier transform for the song

functions:

compile_song: gets the audio data

stem_obj

attributes:

filepath: the path to the file containing the stem?
song: the name of the song
stem: the name of the stem
audio: audio object, the audio data for this stem
fourier (optional): a fourier object, the fourier tranform for the stem

functions:

none

audio_obj

attributes:

data: the numpy array of data for the audio
sr: the samplerate of the audio data

functions:

write_audio_to_file: writes audio to file

fourier_obj

attributes:

freqs
times
fourier
sr
audio

functions:

plot: plots the log of the fourier (log is for visual clarity)
fourier_to_audio: converts the fourier and sr to an audio object, initializes the audio attribute
In [0]:
class song_obj:  # SONG OBJECT (collection of stems)
  def __init__(self, stem_list, name=None, get_fourier=False):
    self.stem_list = stem_list
    if name != None:
      self.name = name
    else:
      self.name= stem_list[0].song_name
    self.audio = self.compile_song()  # returns an audio object
    if(get_fourier==True):
      self.fourier = fourier_obj(self)
    else:
      self.fourier = None
  
  def compile_song(self):
    sr_list = [stem.audio.sr for stem in self.stem_list]
    assert(all(x == sr_list[0] for x in sr_list))
    # dim1 = 15000000  # just to ensure all files have same shape
    data_list = [stem.audio.data for stem in self.stem_list]
    # assert(max([data.shape[0] for data in data_list]) < dim1)
    # dim2 = max([data.shape[1] for data in data_list])
    # audio = np.zeros((dim1))
    length = max([data.shape[0] for data in data_list])
    audio = np.zeros(length)
    for data in data_list:
      audio[:data.shape[0]] += data  # , :data.shape[1]] += data
    return audio_obj(data=audio, sr=sr_list[0])
  
  def get_stem_splits(self):
    vocals_stem_list = []
    perc_stem_list = []
    bass_stem_list = []
    other_stem_list = []
    for stem in self.stem_list:
      stem_group = stem_dict[stem.stem_name]
      if stem_group == 'Vocals':
        vocals_stem_list.append(stem)
      elif stem_group == 'Percussion':
        perc_stem_list.append(stem)
      elif stem_group == 'Bass':
        bass_stem_list.append(stem)
      elif stem_group == 'Other':
        other_stem_list.append(stem)
      else:
        print('warning: ', stem.stem_name, 'was not added to any group')
    return [song_obj(vocals_stem_list, name='Vocals'), 
            song_obj(perc_stem_list, name='Percussion'),
            song_obj(bass_stem_list, name='Bass'),
            song_obj(other_stem_list, name='Other')]


class stem_obj:  # STEM OBJECT
  def __init__(self, song_name, stem_name, get_fourier=False):
    self.filepath = os.path.join('Raw', song_name, stem_name)
    self.song_name = song_name
    self.stem_name = stem_name
    self.audio = audio_obj(filepath=self.filepath)
    if(get_fourier==True):
      self.fourier = fourier_obj(self.audio)
    else:
      self.fourier = None

class audio_obj:  # AUDIO OBJECT
  def __init__(self, data=None, sr=None, filepath=-1):
    # one of (audio, sr) or (filepath) is required
    # this is often the cause of problems if wrong if statement entered
    if filepath != -1:
      self.data, self.sr = soundfile.read(filepath)
      if len(self.data.shape) >= 2:  # stereo
        self.data = self.data[:, 0]  # need to think about stereo handling
        # self.data = np.mean(data, axis=1)  # i think this is correct
    else:
      assert isinstance(data, np.ndarray), "specify filepath or data"
      self.data = data
      self.sr = sr
      if isinstance(data, tuple):
        self.data = self.data[0]
    assert self.data.ndim == 1, "audio data must be 1d"

  def to_fourier(self):  # convert to fourier
    self.fourier = fourier_obj(self)
    return self.fourier
  
  def to_file(self, filepath):  # save as .wav
    soundfile.write(filepath, self.data, self.sr)
  
  def boxplot(self):  # boxplot of the spectrum of values in this audio
    plt.boxplot(self.data)

class fourier_obj:  # FOURIER OBJECT
  def __init__(self, audio=-1,  # input is either this line
               fourier=-1, freqs=-1, t=-1, sr=44100,  # or this line
               abs=False, get_2d=True, get_ri=True):
    # freqs : frequencies : freqs corresponding to 1st axis of fourier
    # t     : times       : times corresponding to 2nd axis of fourier
    # fourier : fourier series : 2d np array of fourier
    assertion = not isinstance(audio, int) or not isinstance(fourier, int)
    assert assertion, "must either specify audio_obj or fourier array"
    if isinstance(fourier, int):
      self.m = 1024
      [self.freqs, self.t, fourier] = signal.stft(audio.data,
                                                       nperseg=self.m,
                                                       noverlap=3*self.m//4)
      self.sr = sr
      self.fourier_ri = self.get_two_channel_fourier(fourier)
    else:  # if just passing in a fourier to make the object
      if fourier.ndim == 2:
        fourier = fourier
        self.m = fourier.shape[0] * 2 - 2
        self.freqs = freqs
        self.t = t
        self.sr = sr
        self.fourier_ri = self.get_two_channel_fourier(fourier)
      else:  # the fourier defined is actually the fourier_ri
        assert fourier.ndim == 3, "fourier_obj must have ndim 2 or 3"
        self.fourier_ri = fourier
        # self.fourier = self.ri_to_complex()
        self.m = self.fourier_ri.shape[0] * 2 - 2
        self.freqs = freqs
        self.t = t
        self.sr = sr
    if abs == True:
      self.fourier = np.abs(self.fourier)
    assert self.fourier_ri.ndim == 3, "fourier ri must be 3d"
  
  def get_two_channel_fourier(self, fourier):
    # for real in first channel, imaginary in second
    f = np.zeros(fourier.shape + tuple((2,)))
    f[:, :, 0] = np.real(fourier)
    f[:, :, 1] = np.imag(fourier)
    return f
  
  def ri_to_complex(self):
    # convert the 2-channel fourier to a standard 2d fourier
    real = np.array(self.fourier_ri[:, :, 0], dtype=complex)
    imag = np.array(self.fourier_ri[:, :, 1], dtype=complex) * 1j
    return real + imag
  
  def to_audio(self):  # convert to audio
    fourier = self.ri_to_complex()
    ts, data = signal.istft(fourier, self.sr,
                            nperseg=self.m, noverlap=3*self.m//4)
    self.audio = audio_obj(data=data, sr=self.sr)
    return self.audio
  
  def to_file(self, songname, filename):  # save as jpg
    path = os.path.join('Train', songname, 'TRANSFORM', filename + '.jpg')
    scipy.misc.imsave(path, self.fourier)
  
  def plot(self):
    # log makes the plot more readable
    fourier_like = np.log(np.abs(self.fourier).clip(min=0.0000000001))
    plt.pcolormesh(self.t, self.freqs, fourier_like)
    plt.ylabel('Frequency [Hz]')
    plt.xlabel('Time [sec]')
    plt.show()
In [0]:
stem_dict_init = {
    'Vocals'     : ['BG Vox', 'Lead Vox', 'Vox Chop', 'Vocals', 
                    'Ahh Stem', 'Drew Ad Lib Stem', 'Drew LD Vox Stem',
                    'Emily Vox Stem', 'Gang Vocal Stem', 'Hook Stem', 
                    'Vocoder Stem', 'Vox Chop Stem', 'Vox Pad Stem',
                    'Vocal Filter', 'Vocal Glitch'],
    'Percussion' : ['Drums', 'Claps', 'Hi Hats', 'Snares', 'Crash Stem',
                    'Hi Hats Stem', 'Kick Stem', 'SFX Stem', 'Snare Stem',
                    'Toms Stem', 'Clap', 'Hi Hats & Cymbals', 'Impacts',
                    'Kick', 'Live Drums', 'Percussion', 'Toms'],
    'Bass'       : ['Bass', '808', 'Bass Stem', 'Wide Bass Stem', 
                    'Live Bass'],
    'Other'      : ['Glitch Synth', 'GTR', 'Piano', 'Sax', 'SFX',
                    'Strings & Slide GTR', 'Synth Chords',
                    'Guitars', 'Risers', 'Synths', 'Arp Stem',
                    'BGV Stem', 'GTR Stem', 'Horns Stem', 'Keys Stem',
                    'Pad Stem', 'Piano Stem', 'Pluck Stem', 
                    'Sax Pad Stem', 'Sax Stem', 'Strings Stem', 
                    'Acoustic Guitar', 'Arp Synth', 'Drop Layers',
                    'Drop Synth 2', 'E Guitar 2', 'Fills', 'Final Synth',
                    'Piano 1', 'Piano 2', 'Wurli']
}

stem_dict = {}  # 
for group in stem_dict_init.keys():
  for stem in stem_dict_init[group]:
    stem_dict[stem + '.wav'] = group
In [0]:
def compile_tracks_and_stems():
  # loops through each song, makes (1) the full track file
  #                                (2) the vocal, perc, bass, other tracks
  for song in os.listdir(data_dir):  # loop through each song
    print('*****' + song)
    stems = []  # intialize what will be a nested list of stem objects

    # (1) make the full track file
    for stem in os.listdir(os.path.join(data_dir, song)):  # stems in song
      # print('*****   ', song, '*****  ', stem)
      temp = stem_obj(song, stem, get_fourier=False)
      stems.append(temp)  # add current stem to the list
    song = song_obj(stem_list=stems)
    song.audio.write_to_file(os.path.join('Train',  # full song file
                                          song.name,
                                          'FULL_TRACK/song.wav'))
    
    # (2) make the vocal, perc, bass, other tracks
    for stem_split in song.get_stem_splits():
      # write the full stems into files
      stem_split.audio.write_to_file(os.path.join('Train',  # stemgroup file
                                                  song.name,
                                                  'STEMS', 
                                                  stem_split.name + '.wav'))

# compile_tracks_and_stems()
In [0]:
class koretzky_obj:
  def __init__(self, stem_name, n, is_regression=False):
    self.me = model_essentials(n=n, is_regression=is_regression)
    self.stem_name = stem_name
    first = True
    mew = True
    for i, song in enumerate(os.listdir(data_dir)):
      if song == 'DLMD':
        continue
      if first == True:  # first iteration only
        track = koretzky_track_obj(song, stem_name, self.me)
        inputs = track.full_track.inputs
        targets = track.stem.outputs
        first = False
        del track
        break
        continue
      # second iteration and beyond:
      inputs = np.concatenate((inputs, 
                               self.get_inputs(song_name=song, mew=mew)))
      targets = np.concatenate((targets, 
                                self.get_targets(song_name=song,
                                                 stem_name='Percussion',
                                                 is_reg=self.me.is_regression,
                                                 mew=mew)))
      if i >= 2:
        break
    self.inputs = inputs
    self.targets = targets
    self.model = self.koretzky()
    
  def koretzky(self):
    shape = (self.me.m, self.me.n, 2)
    input_shape = Input(shape=shape, name='input')
    # # concatenated_stems = []
    for i in range(1):
      i = str(i + 1)
      x = Conv2D(32, (3,3), padding='same',input_shape=shape)(input_shape)
      x = LeakyReLU(name='leaky_' + i + '1')(x)
      x = Conv2D(16, (3, 3), padding='same', name='conv16_' + i + '1')(x)
      x = LeakyReLU(name='leaky_' + i + '2')(x)
      x = MaxPooling2D(pool_size=(3, 3), name='max33_' + i + '1')(x)
      x = Dropout(0.25, name='dropout25_' + i + '1')(x)
      x = Conv2D(64, (3, 3), padding='same', name='conv64_' + i)(x)
      x = LeakyReLU(name='leaky_' + i + '3')(x)
      x = Conv2D(16, (3, 3), padding='same', name='conv16_' + i + '2')(x)
      x = LeakyReLU(name='leaky_' + i + '4')(x)
      x = MaxPooling2D(pool_size=(3,3), name='max33_' + i + '2')(x)
      x = Dropout(0.25, name='dropout25_' + i + '2')(x)
      x = Flatten(name='flatten_' + i)(x)
      x = Dense(128, name='dense128_' + i)(x)
      x = LeakyReLU(name='leaky_' + i + '5')(x)
      x = Dropout(0.5, name='dropout50_' + i)(x)
      x = Dense(self.me.m * 2, activation=self.me.activation,
                name='dense513_' + i)(x)
      # # concatenated_stems.append(x)
    # concatenate output layers
    # # merged_output=Concatenate(axis=1,name='concat')(concatenated_stems)
    # connect to single/common input layer
    # # model = Model(input_shape, merged_output)
    model = Model(input_shape, x)
    model.compile(optimizer=self.me.opt,
                  loss=self.me.loss,
                  metrics=['accuracy'])
    return model
  
  def predict_track(self, song):  # koretzky_track_obj
    #  input_obj = koretzky_input_obj(song, self.me)
    input_slices = self.get_inputs(song)  # input_obj.slices.slices  # a numpy array
    print('got input slices')
    fourier = fourier_slices_obj(input_slices).to_fourier(44100).fourier_ri  # input_obj.fourier_obj.fourier_ri
    sr = 44100  # input_obj.sr
    # del input_obj  # delete the object entirely
    print('got fourier_slices')
    preds = self.model.predict(input_slices)
    print('got preds')
    return koretzky_prediction_obj(track=fourier, sr=sr,
                                   model_input=input_slices, 
                                   model_output=preds,
                                   song=song,
                                   stem=self.stem_name,
                                   me=self.me)
    
  def output_slices_to_audio(self):
    x = np.repeat(np.expand_dims(self.outputs[0][0], axis=2), 
                  self.n, axis=2)
    y = np.reshape(x, (x.shape[0], x.shape[1] * x.shape[2]))
    print(y.shape)
    audio = fourier_obj(fourier=y, sr=self.srs[0]).to_audio()
    audio.write_to_file(os.path.join('test_files', 'sliced_up2.wav'))
    print(audio.data.shape)
    print('file saved in test_files/sliced_up2.wav')
    return(audio)
  
  def get_inputs(self, song_name, mew=False):
    # turn the fourier into slices (either with overlapping window
    # or non-overlapping window (mutually_exclusive_windows))
    path = os.path.join('Train', song_name, 'FULL_TRACK', 'song.wav')
    f = fourier_obj(audio_obj(filepath=path)).fourier_ri
    n = self.me.n
    shape = f.shape
    shape1 = shape[1] // n
    fs = f[:, :(n * shape1), :]  # clip for even division
    # fs = np.real(fs)  # HOW TO HANDLE COMPLEX VALUES????
    if mew == False:
      temp_1 = np.zeros((fs.shape[0], fs.shape[1] + n - 1, 2))  # padding
      temp_1[:, (n-1)//2 : temp_1.shape[1] - (n-1)//2, :] += fs
      temp_2 = np.zeros((fs.shape[1], fs.shape[0], n, 2))
      for i in range(temp_2.shape[0]):
        temp_2[i, :, :, :] += temp_1[:, i : i+n, :]  # (i, freq, t, 2)
    else:  # mutually_exclusive_windows == True
      temp_1 = np.transpose(fs, (1, 0, 2))
      temp_2 = np.reshape(temp_1, (temp_1.shape[0] // n,
                                   n, temp_1.shape[1], 2))
    assert temp_2.ndim == 4, "temp_2 must be 4d"
    return temp_2
  
  def get_targets(self, song_name, stem_name, is_reg=False, mew=False):
    path = os.path.join('Train', song_name, 'STEMS', stem_name + '.wav')
    f = fourier_obj(audio_obj(filepath=path)).fourier_ri
    if is_reg == False:  # if using a mask
      mask_thresh = np.percentile(f.flatten(), 97)  # 97th pct threshold
      f = np.where(f > mask_thresh, 1, 0)
    n = self.me.n
    shape = f.shape
    shape1 = shape[1] // n
    fs = f[:, :(n * shape1), :]  # clips for even division
    fs = np.transpose(fs, (1, 0, 2))  # first axis should be timesteps
    if mew == True:
      fs = np.reshape(fs, (n, shape1, fs.shape[1], 2))  # (n, shp1, m, 2)
      fs = fs[(n - 1) // 2, :, :, :]  # (shape1, m) (dim1 vanishes?)
    assert fs.ndim == 3, "fs must be 3d"
    shape = fs.shape
    return np.reshape(fs, (shape[0], shape[1] * shape[2]))

class model_essentials:  # model essential values
  def __init__(self, is_regression=-1, n=-1):
    self.is_regression = is_regression
    self.n = n  # the number of timesteps in each slice
    self.m = 513  # the first axis of fourier
    if is_regression == True:
      self.loss = 'mse'
      self.opt = 'adam'
      self.activation = 'linear'
    else:
      self.loss = 'binary_crossentropy'
      self.opt = 'adam'
      self.activation = 'sigmoid'
In [0]:
############ TRACK OBJECTS

class koretzky_track_obj:
  def __init__(self, song_name, stem, me):
    assert me.n % 2 == 1, "n must be an odd integer"
    self.full_track = koretzky_input_obj(song_name, me, mew=False)
    # self.vocals = koretzky_target_obj(song_name, 'Vocals', 
    #                                   me, self.full_track)
    # self.bass = koretzky_target_obj(song_name, 'Bass', 
    #                                 me, self.full_track)
    self.stem = koretzky_target_obj(song_name, stem, 
                                    me, mew=False)
    # self.other = koretzky_target_obj(song_name, 'Other', 
    #                                  me, self.full_track)
    self.sr = self.full_track.sr

class koretzky_input_obj:
  def __init__(self, song_name, me, mew=False):
    filepath = self.get_path(song_name)
    assert me.n % 2 == 1, "n must be an odd integer"
    self.n = me.n  # the number of samples in each split (25 per koretzky)
    self.track = audio_obj(filepath=filepath)  # start with an audio obj
    self.sr = self.track.sr  # follows from the audio obj
    self.fourier_obj = fourier_obj(audio=self.track)
    self.slices = self.get_slices_obj(mew)  # take slices of the raw track
    data = self.slices.slices
    #r = np.real(data)
    #c = np.imag(data)
    self.inputs = data #tf.complex(r, c)
  
  def get_path(self, song_name):
    return os.path.join('Train', song_name, 'FULL_TRACK', 'song.wav')
  
  def apply_mask(self, mask):
    f = self.fourier_obj
    masked = f.fourier_ri * mask
    f1 = fourier_obj(fourier=masked, freqs=f.freqs, t=f.t, sr=f.sr)
    return f1
  
  def get_slices_obj(self, mutually_exclusive_windows=False):
    # turn the fourier into slices (either with overlapping window
    # or non-overlapping window (mutuall_exclusive_windows))
    f = self.fourier_obj
    n = self.n
    shape = f.fourier_ri.shape
    shape1 = shape[1] // n
    fs = f.fourier_ri[:, :(n * shape1), :]  # clip for even division
    # fs = np.real(fs)  # HOW TO HANDLE COMPLEX VALUES????
    if mutually_exclusive_windows == False:
      temp_1 = np.zeros((fs.shape[0], fs.shape[1] + n - 1, 2))  # padding
      temp_1[:, (n-1)//2 : temp_1.shape[1] - (n-1)//2, :] += fs
      temp_2 = np.zeros((fs.shape[1], fs.shape[0], n, 2))
      for i in range(temp_2.shape[0]):
        temp_2[i, :, :, :] += temp_1[:, i : i+n, :]  # (i, freq, t, 2)
    else:  # mutually_exclusive_windows == True
      temp_1 = np.transpose(fs, (1, 0, 2))
      temp_2 = np.reshape(temp_1, (temp_1.shape[0] // n,
                                   n, temp_1.shape[1], 2))
    assert temp_2.ndim == 4, "temp_2 must be 4d"
    return fourier_slices_obj(temp_2)

class koretzky_target_obj:
  def __init__(self, song_name, stem, me, mew=False):
    assert me.n % 2 == 1, "n must be an odd integer"
    filepath = self.get_path(song_name, stem)
    self.n = me.n
    self.track = audio_obj(filepath=filepath)  # start with an audio obj
    self.sr = self.track.sr  # follows from the audio obj
    if me.is_regression:
      self.fourier_obj = self.get_mask_from_division()
    else:
      self.fourier_obj = self.get_mask_from_pct(97)
    self.slices = self.get_slices_obj(mew)
    shape = self.slices.slices.shape
    self.outputs = self.slices.slices.reshape((shape[0],
                                               shape[1] * 2))
  
  def get_path(self, song_name, stem):
    return os.path.join('Train', song_name, 'STEMS', stem + '.wav')

  def get_mask_from_pct(self, pct):
    f = fourier_obj(audio=self.track)
    mask_thresh = np.percentile(f.fourier_ri.flatten(), pct)
    mask = np.where(f.fourier_ri > mask_thresh, 1, 0)
    return fourier_obj(fourier=mask, freqs=f.freqs, t=f.t, sr=f.sr)
  
  def get_mask_from_division(self):#, full_song):
    #f = fourier_obj(audio=self.track)
    #mask = f.fourier / np.where(full_song.fourier_obj.fourier == 0,
    #                            1e-9, full_song.fourier_obj.fourier)
    # return fourier_obj(fourier=mask, freqs=f.freqs, t=f.t, sr=f.sr)
    return fourier_obj(audio=self.track)  # let's just predict the stem

  def get_slices_obj(self, mutually_exclusive_windows=False):
    f = self.fourier_obj
    n = self.n
    shape = f.fourier_ri.shape
    shape1 = shape[1] // n
    fs = f.fourier_ri[:, :(n * shape1), :]  # clips for even division
    fs = np.transpose(fs, (1, 0, 2))  # first axis should be timesteps
    if mutually_exclusive_windows == True:
      fs = np.reshape(fs, (n, shape1, fs.shape[1], 2))  # (n, shp1, m, 2)
      fs = fs[(n - 1) // 2, :, :, :]  # (shape1, m) (dim1 vanishes?)
    assert fs.ndim == 3, "fs must be 3d"
    return fourier_slices_obj(fs)

class koretzky_prediction_obj:
  def __init__(self, track, sr, model_input, model_output, 
               song, stem, me):
    # self.model_input = model_input
    self.song = song
    self.stem_name = stem
    self.model_output = model_output  # np array of mask pred
    # self.track = track  # koretzky track object of actual track
    target_ri = koretzky_target_obj(song_name=song, 
                                    stem=stem, 
                                    me=me).fourier_obj.fourier_ri
    slices = fourier_slices_obj(self.model_output)  # slices obj of MO
    fourier = slices.to_fourier(sr=sr)  # fourier obj of MO
    self.sr = sr  # sr
    self.vox_preds = fourier.fourier_ri  # fourier ri data of MO
    if me.is_regression == False:
      self.target_audio = self.apply_mask(target_ri, 
                                          track)
      self.predicted_audio = self.apply_mask(self.vox_preds, track)
    else:  # regression == True
      temp = fourier_slices_obj(model_input)
      #self.input_audio = temp.to_fourier(sr=sr).fourier_ri
      #targets = fourier_slices_obj(self.target.outputs)
      self.target_audio = target_ri#  .to_fourier(sr=sr).fourier_ri  # np array
      self.predicted_audio = self.vox_preds
  
  def apply_mask(self, target, track):
    vox = np.zeros(track.shape)
    vox[:min(vox.shape[0], target.shape[0]), 
        :min(vox.shape[1], target.shape[1]), 
        :] = target[:min(vox.shape[0], target.shape[0]),
                    :min(vox.shape[1], target.shape[1]), :]
    return track * vox
  
  def to_folder(self, folder_path):
    path = os.path.join('test_files', self.song, folder_path)
    if os.path.exists(path):
      print('path already exists - deleting the old one')
      shutil.rmtree(path)
    os.mkdir(path)
    # write the input to audio
    # audio = fourier_obj(fourier=self.input_audio,
    #                     sr=self.sr).to_audio()
    # audio.to_file(os.path.join(path, 'input.wav'))
    # write the prediction to audio
    audio = fourier_obj(fourier=self.predicted_audio, 
                        sr=self.sr).to_audio()
    audio.to_file(os.path.join(path, self.stem_name + '.wav'))
    del audio
    # write the output to audio
    audio = fourier_obj(fourier=self.target_audio,
                        sr=self.sr).to_audio()
    audio.to_file(filepath=os.path.join(path, 'actual.wav'))


class fourier_slices_obj:
  def __init__(self, slice_array):#, ts_array, freqs_array):
    if slice_array.ndim == 2:  # need to unflatten the output
      shape = slice_array.shape
      slice_array = np.reshape(slice_array, (shape[0], 
                                             shape[1] // 2, 2))
    fs = slice_array
    m = slice_array.shape[0]
    self.slices = slice_array
    # self.slice_objs = [fourier_obj(fourier=fs[i, :]) for i in range(m)]
  
  def to_fourier(self, sr):  # convert back to fourier
    slices = np.array(self.slices)
    if len(slices.shape) != 3:  # (m, freq_range, len_slice, channels)
      assert len(slices.shape) == 4, "must have shape len 3 or 4"
      mid = (slices.shape[2] - 1) // 2
      slices = slices[:, :, mid, :]  # should turn into (m, freq_range, 2)
    # slices are singleton columns of fourier (m, freq_range)
    fourier_ri = np.transpose(slices, (1, 0, 2))
    print('creating fourier object')
    return fourier_obj(fourier=fourier_ri, sr=sr)  # will recognize ri

Space below to implement

In [0]:
# loads the tracks, gets the fouriers, apply masks, make slices
n = 25  # width of each slice
k = koretzky_obj('Percussion', n, is_regression=False)
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:541: The name tf.placeholder is deprecated. Please use tf.compat.v1.placeholder instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:66: The name tf.get_default_graph is deprecated. Please use tf.compat.v1.get_default_graph instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:4432: The name tf.random_uniform is deprecated. Please use tf.random.uniform instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:4267: The name tf.nn.max_pool is deprecated. Please use tf.nn.max_pool2d instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:148: The name tf.placeholder_with_default is deprecated. Please use tf.compat.v1.placeholder_with_default instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:3733: calling dropout (from tensorflow.python.ops.nn_ops) with keep_prob is deprecated and will be removed in a future version.
Instructions for updating:
Please use `rate` instead of `keep_prob`. Rate should be set to `rate = 1 - keep_prob`.
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/optimizers.py:793: The name tf.train.Optimizer is deprecated. Please use tf.compat.v1.train.Optimizer instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:3657: The name tf.log is deprecated. Please use tf.math.log instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow_core/python/ops/nn_impl.py:183: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
In [0]:
# use slices as target, fit the model
# slightly worse now cause predicts for both channels separately
k.model.fit(x=k.inputs, y=k.targets, epochs=5)
del k.inputs
del k.targets
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:1033: The name tf.assign_add is deprecated. Please use tf.compat.v1.assign_add instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:1020: The name tf.assign is deprecated. Please use tf.compat.v1.assign instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:3005: The name tf.Session is deprecated. Please use tf.compat.v1.Session instead.

Epoch 1/5
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:190: The name tf.get_default_session is deprecated. Please use tf.compat.v1.get_default_session instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:197: The name tf.ConfigProto is deprecated. Please use tf.compat.v1.ConfigProto instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:207: The name tf.global_variables is deprecated. Please use tf.compat.v1.global_variables instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:216: The name tf.is_variable_initialized is deprecated. Please use tf.compat.v1.is_variable_initialized instead.

WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/keras/backend/tensorflow_backend.py:223: The name tf.variables_initializer is deprecated. Please use tf.compat.v1.variables_initializer instead.

42650/42650 [==============================] - 38s 897us/step - loss: 0.1127 - acc: 0.9676
Epoch 2/5
42650/42650 [==============================] - 23s 543us/step - loss: 0.0823 - acc: 0.9690
Epoch 3/5
42650/42650 [==============================] - 24s 554us/step - loss: 0.0784 - acc: 0.9689
Epoch 4/5
42650/42650 [==============================] - 24s 554us/step - loss: 0.0766 - acc: 0.9688
Epoch 5/5
42650/42650 [==============================] - 24s 553us/step - loss: 0.0757 - acc: 0.9688
In [0]:
# predict slices on a song
pred_obj = k.predict_track('CLOSER')  # RAM spikes and then goes down
got input slices
creating fourier object
got fourier_slices
got preds
creating fourier object
In [0]:
# prediction is saved to /content/drive/My Drive/Stems/test_files/[song_name]/actual.wav
pred_obj.to_folder('test_12_20_331_regression_vRM')
In [0]: